function Environment(rooms, entities) {
	this.rooms = rooms;
	this.entities = entities;
	this.tiles = new Array();
	
	var environment = this;
	rooms.each(function(index, room) {
		room.setEnvironment(environment);
	});
	
	this.render = function() {
		this.renderRooms();
		this.renderEntities();
	}

	this.renderRooms = function() {
		this.rooms.each(function(index, room){
			room.render();
		});
	}

	this.renderEntities = function() {
		this.entities.each(function(index, entity) {
			entity.render();
		});
	}

	this.renderTile = function(newTile, roomType) {
		var id = newTile.id;
		var oldTile = this.tiles[id];
		if(oldTile) {
			if(oldTile.type == "wall" && newTile.type == "floor") {
				if(roomType == "room") {
					$("#" + id).removeClass("wall").addClass("floor");
					oldTile.type = "floor";
				} else if(roomType == "corridor") {
					$("#" + id).removeClass("wall").addClass("door");
					oldTile.type = "door";
				}
			}
		} else {
			$("#viewport").append("<div id='" + id + "' class='scenery " + newTile.type + "' " +
					"style='left: " + (newTile.x * 32) + "px; top: " + (newTile.y * 32) + "px;'></div>");
			this.tiles[id] = newTile;
		}
	}
	
	this.tileAt = function(x, y) {
		return this.tiles["tile" + x + "-" + y];
	}
	
	this.entityAt = function(x, y) {
		var result = null;
		this.entities.each(function(index, entity) {
			if(entity.x == x && entity.y == y) {
				result = entity;
			}
		});
		return result;
	}
	
	this.getEntity = function(id) {
		return this.entities[id];
	}
	
	this.removeEntity = function(id) {
		delete this.entities[id];
	}
	
	this.findPath = function(tileFrom, tileTo) {
		var nodes = new Array();
		var entityAt = this.entityAt;
		this.tiles.each(function(index, tile) {
			if((tile.type == "floor" || tile.type == "door") && 
					(entityAt(tile.x, tile.y) == null || entityAt(tile.x, tile.y) == player)) {
				if(!nodes[tile.x]) {
					nodes[tile.x] = new Array();
				}
				nodes[tile.x][tile.y] = tile;
			}
		});
		return AStar.search(nodes, tileFrom, tileTo);
	}
}

function Tile(type, x, y) {
	this.x = x;
	this.y = y;
	this.type = type;
	this.id = "tile" + x + "-" + y;
	
	this.isAt = function(x, y) { 
		return (x == this.x) && (y == this.y); 
	}
}

function Room(x1, y1, x2, y2, type) {
	this.x1 = x1;
	this.y1 = y1;
	this.x2 = x2;
	this.y2 = y2;
	this.type = type;

	this.render = function() {
		for(var i = this.x1 - 1; i < this.x2 + 1; i++) {
			this.environment.renderTile(new Tile("wall", i, this.y1 - 1), this.type);
		}
		for(var i = this.y1; i < this.y2; i++) {
			this.environment.renderTile(new Tile("wall", this.x1 - 1, i), this.type);
			for(var j = this.x1; j < this.x2; j++) {
				this.environment.renderTile(new Tile("floor", j, i), this.type);
			}
			this.environment.renderTile(new Tile("wall", this.x2, i), this.type);
		}
		for(var i = this.x1 - 1; i < this.x2 + 1; i++) {
			this.environment.renderTile(new Tile("wall", i, this.y2), this.type);
		}
	}

	this.setEnvironment = function(environment) {
		this.environment = environment;
	}
}

function SimpleCreature(id, race, x, y, actionPointsRemaining) {
	this.x = x;
	this.y = y;
	this.id = id;
	this.race = race;
	this.actionPointsRemaining = actionPointsRemaining;

	this.render = function() {
		$("#viewport").append("<div id='" + this.id + "' class='creature " + this.race.toLowerCase() + "' " +
				"style='left: " + (this.x * 32) + "px; top: " + (this.y * 32) + "px;'></div>");
	}

	this.move = function(x, y) {
		this.x += x;
		this.y += y;
		var distance = Math.abs(x != 0 ? x : y);
		$("#" + this.id).animate({ 
	        left: (this.x * 32) + "px",
	        top: (this.y * 32) + "px"
	      }, distance * 100 );
		if(this == player) {
			centerPlayer();
		}
	}
	
	this.kill = function() {
		$("#" + this.id).remove();
		environment.removeEntity(this.id);
	}
}

function goWest() {
	move("WEST");
}

function goNorthwest() {
	move("NORTHWEST");
}

function goNorth() {
	move("NORTH");
}

function goNortheast() {
	move("NORTHEAST");
}

function goEast() {
	move("EAST");
}

function goSoutheast() {
	move("SOUTHEAST");
}

function goSouth() {
	move("SOUTH");
}

function goSouthwest() {
	move("SOUTHWEST");
}

function move(direction) {
	hidePath();
	$.post("/move.action", { "direction": direction }, function(data) {
		handleEvents(data.events);
	}, "json");
}

function attack(creature) {
	hidePath();
	$.post("/attack.action", { "targetId": creature.id }, function(data) {
		handleEvents(data.events);
	}, "json");
}

function moveTo(x, y) {
	hidePath();
	$.post("/moveTo.action", { "x": x, "y": y }, function(data) {
		handleEvents(data.events);
	}, "json");
}

function endTurn() {
	$.post("/endTurn.action", function(data) {
		handleEvents(data.events);
	}, "json");
}

function handleEvents(events) {
	$.each(events, function(i, event) {
		var entity = environment.getEntity(event.entityId);
		if(event.type == "MoveEvent") {
			entity.move(event.x, event.y);
		} else if(event.type == "ActionPointsEvent") {
			var actionPoints = event.actionPointsRemaining;
			entity.actionPointsRemaining = actionPoints;
			if(entity == player) {
				$("#actionPointsRemaining").html(actionPoints);
				if(actionPoints == 0) {
					log("Out of action points!");
				}
			}
		} else if(event.type == "HitEvent") {
			var target = environment.getEntity(event.targetId);
			animateText("- " + event.damage, "beneficial", target.x, target.y);
		} else if(event.type == "MissEvent") {
			var target = environment.getEntity(event.targetId);
			animateText("MISS", "unfortunate", target.x, target.y);
		} else if(event.type == "KilledEvent") {
			var target = environment.getEntity(event.entityId);
			target.kill();
			log("The " + target.race + " is killed.");
		} else if(event.type == "InsufficientActionPointsEvent") {
			animateText("Not enough action points!", "unfortunate", player.x, player.y);
		}
	});
}

function showPath(destination) {
	var playerTile = environment.tileAt(player.x, player.y);
	hidePath();
	var canMove = false;
	if(destination != playerTile) {
		var path = [];
		path = environment.findPath(playerTile, destination);
		if(path.length == 0) {
			$("#path").append("<div class='indicator target unreachable' style='left: " 
					+ (32 * destination.x) + "px; top: " + (32 * destination.y) + "px;'></div>");
		} else {
			var max = player.actionPointsRemaining - 1;
			path.each(function(index, tile) {
				if((index < max && tile == destination) || index == max) {
					$("#path").append("<div class='indicator target near' style='left: " 
							+ (32 * tile.x) + "px; top: " + (32 * tile.y) + "px;'></div>");
				} else if(index < max) {
					$("#path").append("<div class='indicator path near' style='left: " 
							+ (32 * tile.x) + "px; top: " + (32 * tile.y) + "px;'></div>");
				} else if(tile == destination) {
					$("#path").append("<div class='indicator target far' style='left: " 
							+ (32 * tile.x) + "px; top: " + (32 * tile.y) + "px;'></div>");					
				} else {
					$("#path").append("<div class='indicator path far' style='left: " 
							+ (32 * tile.x) + "px; top: " + (32 * tile.y) + "px;'></div>");
				}
			});
			$("#glassPane").css("cursor", "pointer");
			canMove = true;
		}
	}
	return canMove;
}

function centerPlayer() {
	var width = $(window).width() - 32;
	var height = $(window).height() - 132;
	var top = player.y * 32 - height / 2;
	var left = player.x * 32 - width / 2;
	$(document).scrollTo({ top: top + "px", left: left + "px"}, 100);
}

function hidePath() {
	$("#path").empty();
	$("#glassPane").css("cursor", "default");
}

function showUnreachable(tile) {
	$("#path").empty()
		.append("<div class='indicator target unreachable' style='left: " 
				+ (32 * tile.x) + "px; top: " + (32 * tile.y) + "px;'></div>");
}

function log(message) {
	$("#console").append(message + "<br/>");
	$("#console").scrollTo({ top: "100%", left: "0"}, 100);
}

var nextTextId = 0;
function animateText(text, type, x, y) {
	nextTextId++;
	id = "text" + nextTextId;
	$("#viewport").append("<div id='" + id + "' class='animatedText " + type 
			+ "' style='left: " + (32 * x - 96) + "px; top: " + (32 * y - 16) + "px;'>" + text + "</div>");
	$("#" + id).animate({
        top: (y * 32 - 64) + "px",
        opacity: 0
      }, 1500);
	//$("#" + id).remove();
}

function addEventHandlers() {
	$(document).bind("keyup", "left", goWest);
	$(document).bind("keyup", "home", goNorthwest);
	$(document).bind("keyup", "up", goNorth);
	$(document).bind("keyup", "pageup", goNortheast);
	$(document).bind("keyup", "right", goEast);
	$(document).bind("keyup", "pagedown", goSoutheast);
	$(document).bind("keyup", "down", goSouth);
	$(document).bind("keyup", "end", goSouthwest);

	$(document).bind("keyup", "4", goWest);
	$(document).bind("keyup", "7", goNorthwest);
	$(document).bind("keyup", "8", goNorth);
	$(document).bind("keyup", "9", goNortheast);
	$(document).bind("keyup", "6", goEast);
	$(document).bind("keyup", "3", goSoutheast);
	$(document).bind("keyup", "2", goSouth);
	$(document).bind("keyup", "1", goSouthwest);
	
	$(document).bind("keyup", "return", endTurn);
	
	var currentLocation = { x: 0, y: 0 };
	var canMove = false;
	var canAttack = false;
	$("#glassPane").mousemove(function(e) {
		var x = Math.floor(e.pageX / 32);
		var y =  Math.floor(e.pageY / 32);
		if(x != currentLocation.x || y != currentLocation.y) {
			currentLocation.x = x;
			currentLocation.y = y;
			var tile = environment.tileAt(x, y);
			if(tile && (tile.type == "floor" || tile.type == "door")) {
				canMove = showPath(tile);
				var entity = environment.entityAt(x, y);
				canAttack = entity != null && entity != player;
			} else {
				canMove = false;
				hidePath();
			}
		}
	});
	
	$("#glassPane").mouseout(function(e) {
		hidePath();
	});
	
	$("#glassPane").click(function(e) {
		if(canMove) {
			moveTo(currentLocation.x, currentLocation.y);
		} else if(canAttack) {
			attack(environment.entityAt(currentLocation.x, currentLocation.y));
		}
	});
	
	$("#endTurn").click(function(e) {
		endTurn();
	});
	
	$(window).resize(function(e) {
		centerPlayer();
	});
}